%%% @doc
%%%модуль, который работает с mnesia: тесты в tests_table, результаты в states_table
%%% @end
-module(mnesia_storage).
-include("definitions.hrl").

-export([
  start/1,
  stop/0,
  get_all_test_ids/0,
  get_test/1,
  add_test/1,
  insert_samples/0,
  get_state/1,
  get_last_state_for_device/1,
  update_state/1,
  init/1, get_last_state_for_devices/1]).


%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%интерфейс
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

-spec start/1 :: (pid()) -> ok.
start(ServerPid) ->
  register(testStorage, spawn(mnesia_storage, init, [ServerPid])),
  ok.

-spec init/1 :: (pid()) -> ok.
init(ServerPid) ->
  init_mnesia(),
  ServerPid ! {ok, storage_started},
  event_loop().

-spec init_mnesia/0 :: () -> ok.
init_mnesia() ->
  {ok, IsDebug} = application:get_env(debug),
  case IsDebug of
    true ->
      ok = mnesia:delete_schema([node()]),
      ok = mnesia:create_schema([node()]),
      ok = mnesia:start(),
      {atomic, ok} = mnesia:create_table(testsTable, [{disc_copies, [node()]}, {attributes, record_info(fields, testCase)}, {record_name, testCase}]),
      {atomic, ok} = mnesia:create_table(statesTable, [{disc_copies, [node()]}, {attributes, record_info(fields, stateRecord)}, {record_name, stateRecord}]);

    false ->
      ok = mnesia:start()
  end,
  ok = mnesia:wait_for_tables([testsTable, statesTable], 3000).

-spec stop/0 :: () -> ok.
stop() ->
  try
    testStorage ! {stop, self()},
    receive
      testStorage_stopped -> ok
    end
  catch
    error :_ ->
      ok
  end.

-spec get_all_test_ids() -> list(test_id()).
get_all_test_ids() ->
  testStorage ! {all, self()},
  receive
    {testIds, KeyList} -> KeyList
  end.

-spec get_test(test_id()) -> test().
get_test(TestId) ->
  testStorage ! {get, TestId, self()},
  receive
    {yourTest, Test} -> Test
  end.


-spec add_test(test()) -> test_id().
add_test(Test) ->
  testStorage ! {put, Test, self()},
  receive {nextId, Id} when is_integer(Id) -> Id
  end.

-spec insert_samples() -> ok.
insert_samples() ->
  Samples = storage:get_samples(),
  ok = lists:foreach(fun add_test/1, Samples).


-spec get_state/1 :: (state_id()) -> state().
get_state(StateId) ->
  testStorage ! {getState, StateId, self()},
  receive
    {your_state, State} -> State
  end.

-spec get_last_state_for_device/1 :: (device_id()) -> state().
get_last_state_for_device(DeviceId) ->
  testStorage ! {getLastForDevice, DeviceId, self()},
  receive
    {restored, State} -> State
  end.

-spec get_last_state_for_devices/1 :: ([device_id()]) -> list(state()).
get_last_state_for_devices(DeviceIdList) ->
  testStorage ! {getLastForDeviceList, DeviceIdList, self()},
  receive
    {restored_list, StateList} -> StateList
  end.


-spec update_state/1 :: (state()) -> ok.
update_state(State) ->
  testStorage ! {updateState, State, self()},
  ok.

-spec event_loop/0 :: () -> ok.
event_loop() ->
  receive
  %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
  %% testCases messages
  %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
    {get, TestId, From} when is_integer(TestId), is_pid(From) ->
      get_test(TestId, From),
      event_loop();

    {put, Test, From} when is_pid(From) ->
      add_test(Test, From),
      event_loop();

    {all, From} when is_pid(From) ->
      get_all_tests_ids(From),
      event_loop();
  %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
  %% stateRecords messages
  %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
    {getState, StateId, From} when is_pid(From), StateId > 0 ->
      get_state(StateId, From),
      event_loop();

    {updateState, State, From} when is_pid(From) ->
      update_state(State, From),
      event_loop();

    {getLastForDevice, DeviceId, From} when is_pid(From), DeviceId > 0 ->
      Result = get_last_state_for_device_by_id(DeviceId),
      From ! {restored, Result},
      event_loop();

    {getLastForDeviceList, DeviceIdList, From} when is_pid(From), is_list(DeviceIdList) ->
      States = lists:map(
        fun(Id) ->
          get_last_state_for_device_by_id(Id)
        end,
        DeviceIdList),
      From ! {restored_list, States},
      event_loop();

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%% stop
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
    {stop, From} when is_pid(From) ->
      mnesia:dump_tables([testsTable, statesTable]),
      stopped = mnesia:stop(),
      From ! testStorage_stopped,
      ok
  end.

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%реализация
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
-spec get_test/2 :: (test_id(), pid()) -> ok.
get_test(TestId, From) ->
  Tran = fun() ->
    %match_object(#testCase{id = TestId}),
    case mnesia:read({testsTable, TestId}) of

      [] ->
        error(no_test_with_id, TestId);

      [H | _] ->
        H
    end
  end,

  case mnesia:transaction(Tran) of

    {atomic, Result} ->
      From ! {yourTest, Result};

    {aborted, Reason} ->
      error(Reason)
  end,
  ok.

-spec add_test/2 :: (test(), pid()) -> ok.
add_test(Test, From) ->
  NextId = storage:get_next_id(),
  TestToWrite = Test#testCase{id = NextId},
  Tran = fun() ->
    mnesia:write(testsTable, TestToWrite, write), ok
  end,
  case mnesia:sync_transaction(Tran) of
    {atomic, ok} ->
      From ! {nextId, NextId};
    {aborted, Reason} ->
      io:format("~p : add test ~p~n", [?MODULE_STRING, Reason])
  end,
  ok.

-spec get_all_tests_ids/1 :: (pid()) -> ok.
get_all_tests_ids(From) ->
  Tran = fun() -> mnesia:all_keys(testsTable) end,
  Ans = mnesia:transaction(Tran),
  case Ans of
    {atomic, KeyList} ->
      From ! {testIds, KeyList};
    {aborted, Reason} -> error(Reason)
  end,
  ok.

-spec get_state/2 :: (state_id(), pid()) -> ok.
get_state(StateId, From) ->
  Tran = fun() ->
    States = mnesia:read({statesTable, StateId}),
    case States of
      [] ->
        io:format("test_storage:getState: state is not found~n"),
        #stateRecord{id = StateId};

      [H | _] ->
        H
    end
  end,
  case mnesia:transaction(Tran) of
    {aborted, Reason} ->
      error(Reason);
    {atomic, Result} ->
      From ! {your_state, Result}
  end,
  ok.


-spec update_state/2 :: (state(), pid()) -> ok.
update_state(State, _) ->
  Tran = fun() -> mnesia:write(statesTable, State, write), ok end,
%%   if
%%     State#stateRecord.completed ->
%%       io:format("updateCompl ~p~n", [State#stateRecord.deviceId]);
%%     true ->
%%       ok
%%   end,
  case mnesia:transaction(Tran) of
    {atomic, ok} ->
      ok;
    {aborted, Reason} ->
      error(Reason)
  end.

-spec get_last_state_for_device_by_id/1 :: (device_id()) -> state().
get_last_state_for_device_by_id(DeviceId) when is_integer(DeviceId) ->
  Tran = get_tran_last_state_for_device(DeviceId),
  case mnesia:transaction(Tran) of
    {atomic, Result} ->
      Result;
    {aborted, Reason} ->
      error(Reason)
  end.

-spec get_tran_last_state_for_device/1 :: (device_id()) -> fun(() -> state()).
get_tran_last_state_for_device(DeviceId) ->

  fun() ->
    ListStatesForDevice = mnesia:match_object(statesTable, {stateRecord, '_', '_', DeviceId, '_', '_', '_', '_', '_'}, read),
    case ListStatesForDevice of

      [] ->
        %io:format("getLastStateForDevice: no states for this device~n"),
        #stateRecord{id = -1, testId = -1};

      AllStatesForDevice when is_list(AllStatesForDevice) ->

        SelectorId = fun(#stateRecord{id = N1}) ->
          N1% * -1 не быть первым
        end,

        MaxIds = lists:map(SelectorId, AllStatesForDevice),
        MaxId = lists:max(MaxIds),
        NotInt = not is_integer(MaxId),
        if
          NotInt ->
            error("MaxId is not int: ~p~n", [MaxId]);
          true -> ok
        end,

        FindById = fun(#stateRecord{id = N}) ->
          N =:= MaxId
        end,
        hd(lists:filter(FindById, AllStatesForDevice))
    end
  end.